package com.xcm.aop;

import com.alibaba.fastjson.JSONObject;
import com.xcm.annotation.Operate;
import com.xcm.cache.RedisCacheDao;
import com.xcm.constant.CookieConstant;
import com.xcm.constant.FileConstants;
import com.xcm.dao.SysOptLogMapper;
import com.xcm.model.*;
import com.xcm.util.DateUtils;
import com.xcm.util.SessionUtils;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户操作日志
 *
 * @author 林强
 * @date 2018-04-12 11:15
 */
@Aspect
@Component
public class OperateAop {
    private final static Logger logger = LoggerFactory.getLogger(OperateAop.class);

    @Autowired
    private HttpServletRequest request;
    @Autowired
    private SysOptLogMapper sysOptLogMapper;
    @Autowired
    private RedisCacheDao redisCacheDao;
    private ThreadLocal<SysOptLog> sysOptLogThreadLocal = new ThreadLocal<>();

    @Pointcut(value = "@annotation(com.xcm.annotation.Operate)")
    public void logPointcut() {

    }

    @AfterReturning(returning = "result", pointcut = "logPointcut()")
    public void doAfterReturning(Object result) {
        try {
            SysOptLog userOperatLog = sysOptLogThreadLocal.get();
            if (null != result) {
                boolean flag = true;
                if (result instanceof Integer) {
                    if (Integer.parseInt(result.toString()) <= 0) {
                        flag = false;
                    }
                } else if (result instanceof String) {
                    //批量导入用户失败
                    if (!result.toString().contains(FileConstants.IMPORT_SUCCESS)) {
                        flag = false;
                    }
                }
                if (flag) {
                    userOperatLog.setOperateTime(DateUtils.format(new Date()));
                    userOperatLog.setOperate(userOperatLog.getOperate() + ",操作成功");
                    sysOptLogMapper.save(userOperatLog);
                    return;
                }
            }
            userOperatLog.setOperateTime(DateUtils.format(new Date()));
            userOperatLog.setOperate(userOperatLog.getOperate() + ",操作失败");
            sysOptLogMapper.save(userOperatLog);
        } catch (Exception e) {
            logger.error("OperateAop doAfterReturning 操作日志记录失败:", e);
        } finally {
            if (null != sysOptLogThreadLocal) {
                sysOptLogThreadLocal.remove();
            }
        }
    }

    /**
     * 方法前后加入日志
     *
     * @param jp 切点
     */
    @Before(value = "logPointcut()")
    public void doBefore(JoinPoint jp) {
        try {
            insertOpertLog(jp);
        } catch (NotFoundException e) {
            logger.error("OperateAop doBefore 操作日志记录失败:", e);
        }
    }

    /**
     * 插入日志
     *
     * @param jp 切点
     * @throws NotFoundException 未找到资源
     */
    private void insertOpertLog(JoinPoint jp) throws NotFoundException {
        Class<? extends Object> classD = jp.getTarget().getClass();
        String className = classD.getName();
        MethodSignature joinPointObject = (MethodSignature) jp.getSignature();
        Method method = joinPointObject.getMethod();
        String methodName = method.getName();
        if (method.isAnnotationPresent(Operate.class)) {
            SysOptLog userOperatLog = new SysOptLog();
            //获取被切参数名称及参数值
            Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), className, methodName, jp.getArgs());
            if (null != nameAndArgs) {
                userOperatLog.setParams(nameAndArgs.toString());
            }
            userOperatLog.setIp(request.getRemoteAddr());
            userOperatLog.setMethod(className + "." + methodName);
            //操作用户
            SysUser sysUser = SessionUtils.getSessionUser(request);
            if (null != sysUser) {
                userOperatLog.setUserId(sysUser.getUserId());
                userOperatLog.setUserName(sysUser.getUserName());
            } else {
                JSONObject sysUserJson = redisCacheDao.getUserByToken(request, CookieConstant.WHOLE_COOKIE_NAME);
                if (null != sysUserJson) {
                    userOperatLog.setUserName(sysUserJson.getString("userName"));
                    userOperatLog.setUserId(sysUserJson.getInteger("uid"));
                }
            }
            if (method.isAnnotationPresent(Operate.class)) {
                Operate md = method.getAnnotation(Operate.class);
                if (null != md) {
                    userOperatLog.setOperate(md.value());
                }
            }
            sysOptLogThreadLocal.set(userOperatLog);
        }
    }

    /**
     * 通过反射机制 获取被切参数名以及参数值
     *
     * @param cls        类
     * @param clazzName  类名称
     * @param methodName 方法名称
     * @param args       参数数组
     * @return Map
     * @throws NotFoundException
     */
    private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
        Map<String, Object> map = new HashMap<>();

        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            //paramNames即参数名
            map.put(attr.variableName(i + pos), args[i]);
        }
        return map;
    }
}
